#include <stdlib.h>
#include <unistd.h>
#include <string.h> // strerror
#include <stdio.h>  // fprintf
#include <sys/types.h> // getpid thread and process id

#include "thread_pool.h"
#include "util.h"
#include "priority_queue.h"
#include "seats.h"

static void *thread_do_work(void *pool);

void List_seats(void* arg) {
    arg1* argPt = (arg1*)arg;
    list_seats(argPt->connfd, argPt->bufsize);
    free(argPt);
    return;
}

void View_seat(void* arg) {
    arg2* argPt = (arg2*)arg;
    view_seat(argPt->connfd, argPt->bufsize, argPt->seat_num, argPt->customer_num);
    free(argPt);
    return;
}

void Confirm_seat(void* arg) {
    arg2* argPt = (arg2*)arg;
    confirm_seat(argPt->connfd, argPt->bufsize, argPt->seat_num, argPt->customer_num);
    free(argPt);
    return;
}

void Cancel(void* arg) {
    arg2* argPt = (arg2*)arg;
    cancel(argPt->connfd, argPt->bufsize, argPt->seat_num, argPt->customer_num);
    free(argPt);
    return;
}

// Create a threadpool, initialize variables, etc
pool_t *pool_create(int queue_size, int num_threads) {
    pool_t* pool = (pool_t*) malloc(sizeof(pool_t));

    pthread_mutex_init(&(pool->lock), NULL); // remember to call pthread_mutex_destroy!
    pthread_cond_init(&(pool->get_task), NULL); // remember to call pthread_cond_destroy!
    pthread_cond_init(&(pool->fill_task), NULL);

    pool->threads = (pthread_t*) malloc(num_threads * sizeof(pthread_t));
    pool->first_queue = NULL;
    pool->business_queue = NULL;
    pool->coach_queue = NULL;

    pool->first_queue_size = 0;
    pool->business_queue_size = 0;
    pool->coach_queue_size = 0;
    pool->task_queue_size_limit = queue_size;
    pool->thread_count = num_threads;

    int i = 0;
    for(i = 0; i < num_threads; i++)
        pthread_create(&(pool->threads[i]), NULL, thread_do_work, (void*)pool);

    return pool;
}

// Add a task to the threadpool
int pool_add_task(pool_t *pool, void (*function)(void *), void *argument, int priority) {
    int err = 0;

    pthread_mutex_lock(&(pool->lock));

    while(pool->first_queue_size + pool->business_queue_size + pool->coach_queue_size >= pool->task_queue_size_limit) // while it is full
       pthread_cond_wait(&(pool->fill_task), &(pool->lock));

    if (priority == 1)
        insertJob(function, argument, &(pool->first_queue), &(pool->first_queue_size));
    else if (priority == 2)
        insertJob(function, argument, &(pool->business_queue), &(pool->business_queue_size));
    else if (priority == 3)
        insertJob(function, argument, &(pool->coach_queue), &(pool->coach_queue_size));

    pthread_cond_signal(&(pool->get_task));

    pthread_mutex_unlock(&(pool->lock));

    return err;
}

// Destroy the threadpool, free all memory, destroy treads, etc
int pool_destroy(pool_t *pool) {
    int err = 0;

    pthread_mutex_destroy(&(pool->lock));  // deallocate memory
    pthread_cond_destroy(&(pool->get_task)); // deinitialize a condition variable
    pthread_cond_destroy(&(pool->fill_task));

    free(pool->threads);
    emptyJobQueue(&(pool->first_queue));
    emptyJobQueue(&(pool->business_queue));
    emptyJobQueue(&(pool->coach_queue));
    free(pool);

    return err;
}

// Work loop for threads. Should be passed into the pthread_create() method.
// when the thread exit, it will reap itself
// we don't need to reap it in pool_destroy
static void *thread_do_work(void *pool) {
    pthread_detach(pthread_self());

    pool_t* poolPt = (pool_t*) pool;

    // going into the loop means trying to pick a thread to run the task
    while(1) {
        pthread_mutex_lock(&(poolPt->lock));

        while(poolPt->first_queue_size + poolPt->business_queue_size + poolPt->coach_queue_size == 0) //empty
            pthread_cond_wait(&(poolPt->get_task), &(poolPt->lock));

        void (*function)(void *) = NULL;
        void *argument = NULL;

        int i = removeJob(&(poolPt->first_queue), &(poolPt->first_queue_size), &function, &argument);
        if (i == -999)
            i = removeJob(&(poolPt->business_queue), &(poolPt->business_queue_size), &function, &argument);
        if (i == -999)
            i = removeJob(&(poolPt->coach_queue), &(poolPt->coach_queue_size), &function, &argument);

        pthread_cond_signal(&(poolPt->fill_task));

        pthread_mutex_unlock(&(poolPt->lock));

        (*function)(argument); // calling handle_connection
    }
    // going back to next iteration means returning the thread to the pool

   return NULL;
}
